//
//  LFSR.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 19/01/2020.
//  Copyright © 2020 Thomas Harte. All rights reserved.
//

#pragma once

#include <cstdint>
#include <cstdlib>

#include "Sizes.hpp"

namespace Numeric {

template <typename IntType> struct LSFRPolynomial {};

// The following were taken 'at random' from https://users.ece.cmu.edu/~koopman/lfsr/index.html
template <> struct LSFRPolynomial<uint64_t> {
	static constexpr uint64_t value = 0x80000000000019E2;
};

template <> struct LSFRPolynomial<uint32_t> {
	static constexpr uint32_t value = 0x80000C34;
};

template <> struct LSFRPolynomial<uint16_t> {
	static constexpr uint16_t value = 0x853E;
};

template <> struct LSFRPolynomial<uint8_t> {
	static constexpr uint8_t value = 0xAF;
};

/*!
	Provides a linear-feedback shift register with a random initial state; if no polynomial is supplied
	then one will be picked that is guaranteed to give the maximal number of LFSR states that can fit
	in the specified int type.
*/
template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntType>::value> class LFSR {
public:
	/*!
		Constructs an LFSR with a random initial value.
	*/
	constexpr LFSR() noexcept {
		// Randomise the value, ensuring it doesn't end up being 0;
		// don't set any top bits, in case this is a signed type.
		while(!value_) {
			uint8_t *value_byte = reinterpret_cast<uint8_t *>(&value_);
			for(size_t c = 0; c < sizeof(IntType); ++c) {
				*value_byte = uint8_t(uint64_t(rand()) * 127 / RAND_MAX);
				++value_byte;
			}
		}
	}

	/*!
		Constructs an LFSR with the specified initial value.

		An initial value of 0 is invalid.
	*/
	LFSR(const IntType initial_value) : value_(initial_value) {}

	/*!
		Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0,
		determining the bit that was just shifted out.
	*/
	IntType next() {
		const auto result = IntType(value_ & 1);
		value_ = IntType((value_ >> 1) ^ (result * polynomial));
		return result;
	}

private:
	IntType value_ = 0;
};

template <uint64_t polynomial> class LFSRv: public LFSR<min_int_for_value_t<polynomial>, polynomial> {};

}
